#include "font.h"

#include "bitmap.h"
#include "draw.h"
#include "log.h"

#include <ft2build.h>
#include FT_FREETYPE_H

////////////////////////////////////////////////////////////////////////////////

static FT_Library library;

bool FontSystem_init()
{
  if (FT_Init_FreeType(&library))
  {
    return false;
  }
  
  return true;
}

////////////////////////////////////////////////////////////////////////////////

static bool Font_getGlyphDim(FT_Face face, unsigned char_code, Dimension* dim)
{
  unsigned glyph_index;
  
  glyph_index = FT_Get_Char_Index(face, char_code);
  
  if (FT_Load_Glyph(face, glyph_index, 0))
  {
    return false;
  }
  
  if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL))
  {
    return false;
  }
  
  dim->w = face->glyph->bitmap.width;
  dim->h = face->glyph->bitmap.rows;
  
  return true;
}

static bool Font_loadGlyph(FT_Face face, unsigned char_code, Glyph* glyph,
  unsigned pitch, uint8* pix)
{
  unsigned glyph_index;
  
  glyph_index = FT_Get_Char_Index(face, char_code);
  
  if (FT_Load_Glyph(face, glyph_index, 0))
  {
    return false;
  }
  
  if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL))
  {
    return false;
  }
  
  glyph->advance = face->glyph->advance.x >> 6;
  glyph->pix_rect.w = face->glyph->bitmap.width;
  glyph->pix_rect.h = face->glyph->bitmap.rows;
  glyph->offset.x = face->glyph->metrics.horiBearingX >> 6;
  glyph->offset.y = (face->glyph->metrics.horiBearingY >> 6) - (face->size->metrics.ascender >> 6);
  
  for (int y = 0; y < face->glyph->bitmap.rows; y++)
  {
    for (int x = 0; x < face->glyph->bitmap.width; x++)
    {
      pix[pitch * y + x] = face->glyph->bitmap.buffer[face->glyph->bitmap.pitch * y + x];
    }
  }
  
  return true;
}

////////////////////////////////////////////////////////////////////////////////

const char* Font_getErrorMsg(FontError error)
{
  const char* msg = "unknown";
  
  switch (error)
  {
    case FONT_ERROR_OK: msg = "ok"; break;
    case FONT_ERROR_NOT_OPEN_FILE: msg = "could not open file"; break;
    case FONT_ERROR_NOT_SET_SIZE: msg = "could not set size"; break;
    case FONT_ERROR_NOT_GET_GLYPH: msg = "could not get glyph"; break;
  }
  
  return msg;
}

////////////////////////////////////////////////////////////////////////////////

void Font_init(Font* font)
{
  memset(font, 0, sizeof(Font));
}

void Font_free(Font* font)
{
  Graphics_deleteTexture(&font->texture);
  
  Font_init(font);
}

bool Font_loadBmp(Font* font, const char* filename)
{
  Image image;
  
  Image_init(&image);
  
  if (Bitmap_load(&image, filename))
  {
    return false;
  }
  
  Graphics_createTexture(&font->texture, image);
  
  int glyph_width = image.width/ 16;
  int glyph_height = image.height / 6;
  Glyph* glyph = font->glyph;
  
  font->metrics.line_height = glyph_height;
  font->metrics.line_spacing = glyph_height;
  font->tex_dim.w = image.width;
  font->tex_dim.h = image.height;
  
  for (int y = 0; y < 6; y++)
  {
    for (int x = 0; x < 16; x++)
    {
      glyph->advance = glyph_width;
      glyph->offset.x = 0;
      glyph->offset.y = 0;
      glyph->pix_rect.x = glyph_width * x;
      glyph->pix_rect.y = glyph_height * y;
      glyph->pix_rect.w = glyph_width;
      glyph->pix_rect.h = glyph_height;
      
      glyph++;
    }
  }
  
  Image_free(&image);
  
  return true;
}

FontError Font_load(Font* font, unsigned size, const char* filename)
{
  FT_Face face;
  Dimension max_dim(0, 0);
  Dimension tex_dim;
  uint8* pix;
  
  if (FT_New_Face(library, filename, 0, &face))
  {
    return FONT_ERROR_NOT_OPEN_FILE;
  }
  
  if (FT_Set_Pixel_Sizes(face, 0, size))
  {
    return FONT_ERROR_NOT_SET_SIZE;
  }
  
  for (unsigned i = 0; i < 96; i++)
  {
    Dimension glyph_dim;
    
    if (!Font_getGlyphDim(face, 32 + i, &glyph_dim))
    {
      return FONT_ERROR_NOT_GET_GLYPH;
    }
    
    if (glyph_dim.w > max_dim.w)
    {
      max_dim.w = glyph_dim.w;
    }
    
    if (glyph_dim.h >max_dim.h)
    {
      max_dim.h = glyph_dim.h;
    }
  }
  
  tex_dim.w = max_dim.w * 16;
  tex_dim.h = max_dim.h * 6;
  
  pix = new uint8[tex_dim.w * tex_dim.h];
  memset(pix, 0, tex_dim.w * tex_dim.h);
  
  for (unsigned i = 0; i < 96; i++)
  {
    uint8* glyph_pix = &pix[tex_dim.w * (i / 16) * max_dim.h + max_dim.w * (i % 16)];
    
    if (!Font_loadGlyph(face, 32 + i, &font->glyph[i], tex_dim.w, glyph_pix))
    {
      delete[] pix;
      
      return FONT_ERROR_NOT_GET_GLYPH;
    }
    
    font->glyph[i].pix_rect.x = max_dim.w * (i % 16);
    font->glyph[i].pix_rect.y = max_dim.h * (i / 16);
  }
  
  font->metrics.line_height = (face->size->metrics.ascender - face->size->metrics.descender) >> 6;
  font->metrics.line_spacing = face->size->metrics.height >> 6;
  font->tex_dim.w = tex_dim.w;
  font->tex_dim.h = tex_dim.h;
  
  ImageAlpha image;
  
  image.width = tex_dim.w;
  image.height = tex_dim.h;
  image.alpha = pix;
  
  Graphics_createTexture(&font->texture, image);
  
  delete[] pix;
  
  return FONT_ERROR_OK;
}

void Font_getTextDim(const Font& font, const char* text, Dimension* dim)
{
  if (text == NULL)
  {
    dim->set(0, 0);
    
    return;
  }
  
  int len = strlen(text);
  
  dim->w = 0;
  
  for (int i = 0; i < len; i++)
  {
    char c = text[i];
    
    if (c < 32)
    {
      continue;
    }
    
    dim->w += font.glyph[c - 32].advance;
  }
  
  dim->h = font.metrics.line_height;
}

int Font_drawChar(const Font& font, const Point& pos, Colour colour, char c)
{
  Material material;
  Point p;
  
  if (c < 32)
  {
    return 0;
  }
  
  p = pos;
  p.x += font.glyph[c - 32].offset.x;
  p.y -= font.glyph[c - 32].offset.y;
  
  Graphics_initMaterial(&material);
  
  material.colour = colour;
  material.alpha_blend = true;
  material.texture.id = font.texture.id;
  
  Draw_drawSubTexRect(material, font.tex_dim, font.glyph[c - 32].pix_rect, p);
  
  return font.glyph[c - 32].advance;
}

void Font_print(const Font& font, const Point& pos, Colour colour, const char* text)
{
  Point p = pos;
  
  if (text == NULL)
  {
    return;
  }
  
  for (int i = 0; text[i]; i++)
  {
    p.x += Font_drawChar(font, p, colour, text[i]);
  }
}

void Font_print(const Font& font, const Rect& rect, TextHorzAlign horz_align,
  TextVertAlign vert_align, Colour colour, const char* text)
{
  Point pos;
  Dimension dim;
  
  Font_getTextDim(font, text, &dim);
  
  if (horz_align == TEXT_HORZ_ALIGN_LEFT)
  {
    pos.x = rect.x;
  }
  else if (horz_align == TEXT_HORZ_ALIGN_CENTER)
  {
    pos.x = rect.x + (rect.w - dim.w) / 2;
  }
  else
  {
    pos.x = rect.x + rect.w - dim.w;
  }
  
  if (vert_align == TEXT_VERT_ALIGN_TOP)
  {
    pos.y = rect.y;
  }
  else if (vert_align == TEXT_VERT_ALIGN_CENTER)
  {
    pos.y = rect.y + (rect.h - dim.h) / 2;
  }
  else
  {
    pos.y = rect.y + rect.h - dim.h;
  }
  
  Font_print(font, pos, colour, text);
}
